<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC String Channel</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            max-width: 800px;
        }
        #messages {
            border: 1px solid #ccc;
            padding: 10px;
            height: 300px;
            overflow-y: auto;
            margin-bottom: 10px;
            font-family: monospace;
        }
        #messageInput {
            width: 70%;
            padding: 5px;
        }
        button {
            padding: 5px 10px;
            margin-right: 5px;
        }
        .log-info { color: black; }
        .log-error { color: red; }
        .log-success { color: green; }
        .log-debug { color: blue; }
    </style>
</head>
<body>
<h1>WebRTC String Channel</h1>
<button id="startButton" onclick="startWebRTC()">Start WebRTC Connection</button>
<button id="stopButton" onclick="stopWebRTC()" disabled>Stop Connection</button>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Type a message..." disabled>
<button id="sendButton" onclick="sendMessage()" disabled>Send</button>

<script>
    let peerConnection;
    let dataChannel;
    let ws;
    let wsRetryCount = 0;
    let pendingOffer = null;
    let pendingCandidates = [];
    const maxWsRetries = 3;
    const wsRetryInterval = 3000;
    const signalingServerUrl = 'ws://123.56.244.112:9097/ws/server';
    const srcId = 'src001'; // 固定 srcId 为 src001

    function log(message, level = 'info') {
        const timestamp = new Date().toLocaleTimeString('en-US', { hour12: false });
        const messagesDiv = document.getElementById('messages');
        let logClass = 'log-info';
        let logPrefix = '[INFO]';

        if (level === 'error') {
            logClass = 'log-error';
            logPrefix = '[ERROR]';
        } else if (level === 'success') {
            logClass = 'log-success';
            logPrefix = '[SUCCESS]';
        } else if (level === 'debug') {
            logClass = 'log-debug';
            logPrefix = '[DEBUG]';
        }

        messagesDiv.innerHTML += `<p class="${logClass}">${timestamp} ${logPrefix} ${message}</p>`;
        messagesDiv.scrollTop = messagesDiv.scrollHeight;
    }

    function updateUIState(isConnected) {
        document.getElementById('startButton').disabled = isConnected;
        document.getElementById('stopButton').disabled = !isConnected;
        document.getElementById('messageInput').disabled = !isConnected;
        document.getElementById('sendButton').disabled = !isConnected;
    }

    async function startWebRTC() {
        log('Initializing WebRTC connection...');
        updateUIState(false);

        const config = {
            iceServers: [
                {
                    urls: 'stun:114.215.191.181:3478',
                    username: 'robot',
                    credential: 'ppp1234'
                },
                {
                    urls: 'turn:114.215.191.181:3478?transport=tcp',
                    username: 'robot',
                    credential: 'ppp1234'
                }
            ]
        };

        try {
            peerConnection = new RTCPeerConnection(config);
            log('PeerConnection created, configuration: ' + JSON.stringify(config), 'success');

            dataChannel = peerConnection.createDataChannel('string-channel');
            log('Data channel "string-channel" created, id: ' + dataChannel.id, 'success');
            setupDataChannel();

            peerConnection.onicecandidate = (event) => {
                if (event.candidate && event.candidate.candidate) {
                    log('Generated ICE candidate: ' + JSON.stringify(event.candidate), 'debug');
                    if (ws && ws.readyState === WebSocket.OPEN) {
                        sendSignalingMessage({ type: 'candidate', candidate: event.candidate, srcId: srcId });
                    } else {
                        log('Caching ICE candidate due to WebSocket not connected', 'debug');
                        pendingCandidates.push(event.candidate);
                    }
                } else {
                    log('ICE candidate gathering completed or empty candidate', 'debug');
                }
            };

            peerConnection.oniceconnectionstatechange = () => {
                const state = peerConnection.iceConnectionState;
                log(`ICE connection state changed to: ${state}, signalingState: ${peerConnection.signalingState}`, 'info');
                if (state === 'failed') {
                    log('ICE connection failed, restarting WebRTC...', 'error');
                    stopWebRTC();
                    setTimeout(startWebRTC, 1000);
                } else if (state === 'connected' || state === 'completed') {
                    log(`ICE connection established: ${state}`, 'success');
                    updateUIState(true);
                }
            };

            peerConnection.onicegatheringstatechange = () => {
                log(`ICE gathering state: ${peerConnection.iceGatheringState}`, 'debug');
            };

            peerConnection.onsignalingstatechange = () => {
                log(`Signaling state: ${peerConnection.signalingState}`, 'debug');
            };

            peerConnection.ondatachannel = (event) => {
                dataChannel = event.channel;
                log(`Received data channel: ${dataChannel.label}, id: ${dataChannel.id}`, 'success');
                setupDataChannel();
            };

            connectWebSocket();
        } catch (error) {
            log(`Failed to initialize WebRTC: ${error}`, 'error');
            updateUIState(false);
        }
    }

    function stopWebRTC() {
        if (dataChannel) {
            dataChannel.close();
            log('Data channel closed', 'success');
        }
        if (peerConnection) {
            peerConnection.close();
            log('PeerConnection closed', 'success');
        }
        if (ws) {
            ws.close();
            log('WebSocket closed', 'success');
        }
        ws = null;
        peerConnection = null;
        dataChannel = null;
        wsRetryCount = 0;
        pendingOffer = null;
        pendingCandidates = [];
        updateUIState(false);
    }

    function connectWebSocket() {
        if (ws && ws.readyState === WebSocket.OPEN) {
            log('WebSocket already connected, state: ' + ws.readyState, 'info');
            return;
        }
        log('Attempting to connect to WebSocket: ' + signalingServerUrl);
        ws = new WebSocket(signalingServerUrl);

        ws.onopen = () => {
            wsRetryCount = 0;
            log('WebSocket connected to ' + signalingServerUrl, 'success');
            // startWebSocketPing();
            if (peerConnection) {
                log('Creating new offer after WebSocket connection', 'info');
                createOffer();
            } else {
                log('No PeerConnection, restarting WebRTC...', 'info');
                startWebRTC();
            }
        };

        ws.onmessage = (event) => {
            log(`Received WebSocket message: ${event.data}`, 'debug');
            handleSignalingMessage(event);
        };

        ws.onclose = (event) => {
            log(`WebSocket connection closed, code: ${event.code}, reason: ${event.reason}`, 'error');
            updateUIState(false);
            if (wsRetryCount < maxWsRetries) {
                wsRetryCount++;
                log(`Retrying WebSocket connection (${wsRetryCount}/${maxWsRetries})...`);
                setTimeout(connectWebSocket, wsRetryInterval);
            } else {
                log('Max WebSocket retry attempts reached', 'error');
            }
        };

        ws.onerror = (error) => {
            log(`WebSocket error: ${JSON.stringify(error)}`, 'error');
        };
    }

    function startWebSocketPing() {
        setInterval(() => {
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ type: 'ping', srcId: srcId }));
                log('Sent ping message', 'debug');
            }
        }, 5000);
    }

    function setupDataChannel() {
        dataChannel.onopen = () => {
            log(`Data channel opened, state: ${dataChannel.readyState}`, 'success');
            updateUIState(true);
            try {
                dataChannel.send('Hello from HTML client!');
                log('Sent initial message: Hello from HTML client!', 'success');
            } catch (error) {
                log(`Failed to send initial message: ${error}`, 'error');
            }
        };
        dataChannel.onmessage = (event) => {
            log(`Received data channel message: ${event.data}`, 'success');
        };
        dataChannel.onclose = () => {
            log(`Data channel closed, state: ${dataChannel.readyState}`, 'error');
            updateUIState(false);
        };
        dataChannel.onerror = (error) => {
            log(`Data channel error: ${error}`, 'error');
        };
    }

    async function createOffer() {
        try {
            log('Creating offer...', 'info');
            const offer = await peerConnection.createOffer();
            await peerConnection.setLocalDescription(offer);
            log('Offer created and set as local description, SDP: ' + offer.sdp.substring(0, 100) + '...', 'success');
            pendingOffer = { type: 'offer', sdp: btoa(offer.sdp), srcId: srcId };
            sendSignalingMessage(pendingOffer);
            // 发送缓存的 ICE 候选
            while (pendingCandidates.length > 0) {
                const candidate = pendingCandidates.shift();
                log('Sending cached ICE candidate: ' + JSON.stringify(candidate), 'debug');
                sendSignalingMessage({ type: 'candidate', candidate, srcId: srcId });
            }
        } catch (error) {
            log(`Error creating offer: ${error}`, 'error');
        }
    }

    async function handleSignalingMessage(event) {
        try {
            const message = JSON.parse(event.data);
            log(`Parsed signaling message: ${JSON.stringify(message)}`, 'debug');

            switch (message.type) {
                case 'answer':
                    const sdp = atob(message.sdp);
                    log('Received answer SDP: ' + sdp.substring(0, 100) + '...', 'debug');
                    await peerConnection.setRemoteDescription({
                        type: 'answer',
                        sdp
                    });
                    log('Set remote description (answer)', 'success');
                    break;
                case 'candidate':
                    if (message.candidate && message.candidate.candidate) {
                        log('Received ICE candidate: ' + JSON.stringify(message.candidate), 'debug');
                        await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
                        log('Added ICE candidate', 'success');
                    } else {
                        log('Ignored invalid ICE candidate: ' + JSON.stringify(message.candidate), 'debug');
                    }
                    break;
                case 'error':
                    log(`Server error: ${message.sdp}`, 'error');
                    break;
                default:
                    log(`Ignoring unknown message type: ${message.type || 'undefined'}`, 'debug');
            }
        } catch (error) {
            log(`Error handling signaling message: ${error}, raw message: ${event.data}`, 'error');
        }
    }

    function sendSignalingMessage(message) {
        if (ws && ws.readyState === WebSocket.OPEN) {
            // 添加 srcId 和 tarId（tarId 设为空，视后端需求可调整）
            message.srcId = srcId;
            message.tarId = ''; // 可根据后端需求设置目标 ID
            const messageStr = JSON.stringify(message);
            ws.send(messageStr);
            log(`Sent signaling message: ${messageStr}`, 'success');
        } else {
            log(`WebSocket not connected, caching message: ${JSON.stringify(message)}`, 'error');
            if (message.type === 'offer') {
                pendingOffer = { ...message, srcId: srcId, tarId: '' };
            } else if (message.type === 'candidate') {
                pendingCandidates.push(message.candidate);
            }
        }
    }

    function sendMessage() {
        const input = document.getElementById('messageInput');
        const message = input.value.trim();
        if (message && dataChannel && dataChannel.readyState === 'open') {
            try {
                dataChannel.send(message);
                log(`Sent data channel message: ${message}`, 'success');
                input.value = '';
            } catch (error) {
                log(`Error sending data channel message: ${error}`, 'error');
            }
        } else {
            log('Data channel not open or message empty', 'error');
        }
    }
</script>
</body>
</html>